/*
 * Copyright Radosław Smogura, 2012. All rights reserved.
 *
 * The content of this file is confidential and it is intelectual property
 * of copyright owners.
 * 
 *
 * $URL: http://reprap-ide.googlecode.com/svn/trunk/RepRapIDE/RepRapProject/src/main/java/eu/softper/reprap/utils/HashMapCustomized.java $
 * $Id: HashMapCustomized.java 3 2012-11-19 10:58:30Z mail@smogura.eu $
 * $Revision: 3 $
 */
package eu.softper.reprap.utils;

import java.math.BigDecimal;
import java.util.*;

/** Hash map where key is a number. Normal hash map is not enaugh for our
 * purposes, becaus of that same number may have different hash codes, even
 * {@link BigDecimal} which are numerically equal may have different hash codes,
 * as well their equals may return falses.
 * 
 * To resolve this problem the wrapped class for Number is used,
 * this class will use intValue method to determine hashCode (which may be not
 * usefull when with fractional only numbers). Equality of such wrapped
 * object is determined by comparation insted of base equal.
 * 
 * <br/>
 * <b>Notes</b>
 * This class wraps number key in additional object, so memory usage will be
 * slightly greater. In addition get, and puts creates short living objects
 * for testing desired value.
 * 
 * @author rsmogura
 */
public class HashMapCustomized<K, V> implements Map<K, V> {
    private final Map<KeyWrapper, V> backed;
    private final EqualityProvider<K> eqProvieder;
    
    public HashMapCustomized(EqualityProvider<K> equalityProvider) {
        backed = new HashMap<KeyWrapper, V>();
        this.eqProvieder = equalityProvider;
    }
    
    @Override
    public int size() {
        return backed.size();
    }

    @Override
    public boolean isEmpty() {
        return backed.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        if (!(key instanceof Number) && !(key instanceof Comparable))
            return false;
        K k = (K) key;
        return backed.containsKey(new KeyWrapper(k));
    }

    @Override
    public boolean containsValue(Object value) {
        return backed.containsValue(value);
    }

    @Override
    public V get(Object key) {
        if (!(key instanceof Number) && !(key instanceof Comparable))
            return null;
        K k = (K) key;
        return backed.get(new KeyWrapper(k));
    }

    @Override
    public V put(K key, V value) {
        return backed.put(new KeyWrapper(key), value);
    }

    @Override
    public V remove(Object key) {
        if (!(key instanceof Number) && !(key instanceof Comparable))
            return null;
        K k = (K) key;
        return backed.remove(new KeyWrapper(k));
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        for(Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            backed.put(new KeyWrapper(e.getKey()), e.getValue());
        }
    }

    @Override
    public void clear() {
        backed.clear();
    }

    @Override
    public Set<K> keySet() {
        return new KeySetWrapper(backed.keySet());
    }

    @Override
    public Collection<V> values() {
        return backed.values();
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        return new EntrySetWrapper(backed.entrySet());
    }
    
    protected class KeyWrapper {
        private K value;

        public KeyWrapper(K value) {
            this.value = value;
        }
        
        @Override
        public int hashCode() {
            return eqProvieder.hashCode(value);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj.getClass() != this.getClass())
                return false;
            KeyWrapper other = (KeyWrapper) obj;
            return eqProvieder.equals(this.value, other.value);
        }
    }

    protected class KeyIterator implements Iterator<K>  {
        private Iterator<KeyWrapper> delegate;

        public KeyIterator(Iterator<KeyWrapper> delegate) {
            this.delegate = delegate;
        }
                
        @Override
        public boolean hasNext() {
            return delegate.hasNext();
        }

        @Override
        public K next() {
            return delegate.next().value;
        }

        @Override
        public void remove() {
            delegate.remove();
        }        
    }
    
    protected class KeySetWrapper extends AbstractSet<K> {
        private Set<KeyWrapper> delegate;

        public KeySetWrapper(Set<KeyWrapper> delegate) {
            this.delegate = delegate;
        }
        
        @Override
        public Iterator<K> iterator() {
            return new KeyIterator(delegate.iterator());
        }

        @Override
        public int size() {
            return delegate.size();
        }

        @Override
        public boolean contains(Object o) {
            if (!(o instanceof Number) && !(o instanceof Comparable))
                return false;
            K k = (K) o;
            return super.contains(new KeyWrapper(k));
        }
    }
    
    protected class EntrySetIterator implements Iterator<Map.Entry<K, V>>  {
        private Iterator<Map.Entry<KeyWrapper, V>> delegate;

        public EntrySetIterator(Iterator<Map.Entry<KeyWrapper, V>> delegate) {
            this.delegate = delegate;
        }
                
        @Override
        public boolean hasNext() {
            return delegate.hasNext();
        }

        @Override
        public Map.Entry<K, V> next() {
            return new EntryWrapper(delegate.next());
        }

        @Override
        public void remove() {
            delegate.remove();
        }        
    }
    
    protected class EntrySetWrapper extends AbstractSet<Map.Entry<K, V>> {
        private Set<Map.Entry<KeyWrapper, V>> delegate;

        public EntrySetWrapper(Set<Map.Entry<KeyWrapper, V>> delegate) {
            this.delegate = delegate;
        }
        
        @Override
        public Iterator<Map.Entry<K, V>> iterator() {
            return new EntrySetIterator(delegate.iterator());
        }

        @Override
        public int size() {
            return delegate.size();
        }
    }
    
    protected class EntryWrapper implements Map.Entry<K, V> {
        Map.Entry<KeyWrapper, V> delegate;

        public EntryWrapper(Entry<KeyWrapper, V> delegate) {
            this.delegate = delegate;
        }

        @Override
        public V setValue(V value) {
            return delegate.setValue(value);
        }

        @Override
        public V getValue() {
            return delegate.getValue();
        }

        @Override
        public K getKey() {
            return delegate.getKey().value;
        }

        @Override
        public int hashCode() {
            return delegate.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            if (o.getClass() != getClass())
                return false;
            
            return delegate.equals(((EntryWrapper) o).delegate);
        }

        @Override
        public String toString() {
            return delegate.toString();
        }
    }
    
    public static interface EqualityProvider<K> {
        public int hashCode(K object);
        public boolean equals(K a, K b);
    }
    
    public static class NumberEqualityProvider<K extends Number & Comparable<K>> implements EqualityProvider<K> {
        private final double scale;

        public NumberEqualityProvider() {
            this(1.0);
        }

        public NumberEqualityProvider(double scale) {
            this.scale = scale;
        }
        
        @Override
        public int hashCode(K object) {            
            return (int) (object.doubleValue() * scale);
        }

        @Override
        public boolean equals(K a, K b) {
            return a.compareTo(b) == 0;
        }
    }
}
